/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: cbus_message_packet.c
 *
 * Purpose: implementation message serialize/deserialize
 *
 * Developer:
 *   wen.gu , 2022-03-13
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/bus/cbus_message_packet.h"

#include <string.h>

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
/** +2 indicat start code(1byte), end code(1byte) */
#define CBUS_MSG_PKT_HDR_SIZE (CBUS_MSG_HDR_SIZE + 2)

#define CBUS_MSG_PKT_OFFSET_START_CODE    (0)
#define CBUS_MSG_PKT_OFFSET_LENGTH        (1)
#define CBUS_MSG_PKT_OFFSET_MESSAGE_ID    (5)
#define CBUS_MSG_PKT_OFFSET_SESSION_ID    (7)
#define CBUS_MSG_PKT_OFFSET_PROTO_VER     (9)
#define CBUS_MSG_PKT_OFFSET_INTERFACE_VER (10)
#define CBUS_MSG_PKT_OFFSET_MESSAGE_TYPE  (11)
#define CBUS_MSG_PKT_OFFSET_RETURN_CODE   (12)

#define CBUS_MSG_PKT_SET(data_buf, set_offset, data_type, set_value) *((data_type*)(data_buf + set_offset)) = set_value
#define CBUS_MSG_PKT_CPY(data_buf, data_offset, data_ptr, data_size) memcpy(data_buf + data_offset, data_ptr, data_size)

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef enum _cbus_message_parse_state
{
    CBUS_MSG_PARSE_STATE_START = 0,  /** check start code, then switch to 'Header' */
    CBUS_MSG_PARSE_STATE_HEADER,     /** receive and parse Header, then switch to 'Payload' */
    CBUS_MSG_PARSE_STATE_PAYLOAD,    /** receive payload data, then switch to 'End' */
    CBUS_MSG_PARSE_STATE_END,        /** check end code, if success then do callback. both success and fail then siwtch to 'Start' */
}cbus_message_parse_state_t;


struct _cbus_message_packet_deserializer
{
    cbus_message_parse_state_t state;
    GU32 header_read_pos;
    GU32 payload_read_pos;
    GU32 payload_size;
    
    cbus_message_packet_deserializer_on_message_func on_message;
    GPTR opaque;
    GPTR endpoint_id;
    GU08 header_buf[CBUS_MSG_HDR_SIZE]; 
    cbus_message_t message;
};
/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/
static const GU08* cbus_message_packet_deserializer_on_parse_start(cbus_message_packet_deserializer_t parser, const GU08* data_ptr, const GU08* end_ptr)
{
    //LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    while (data_ptr < end_ptr)
    {
        if (*data_ptr == CBUS_MSG_PKT_START_CODE)
        {
            data_ptr++;
            parser->header_read_pos = 0;
            parser->message = NULL;
            parser->state = CBUS_MSG_PARSE_STATE_HEADER;
            break;
        }

        data_ptr++;
    }

    return data_ptr;
}

static const GU08* cbus_message_packet_deserializer_on_parse_header(cbus_message_packet_deserializer_t parser, const GU08* data_ptr, const GU08* end_ptr)
{
    //LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    GS32 size = (GS32)(end_ptr - data_ptr);
    const GU08* parse_buf = NULL;
    
    if ((parser->header_read_pos == 0) && (size >= CBUS_MSG_HDR_SIZE))
    {
        parse_buf = data_ptr;
        data_ptr += CBUS_MSG_HDR_SIZE;
    }
    else if (parser->header_read_pos < CBUS_MSG_HDR_SIZE)
    {
    
        GS32 cpSize = CBUS_MSG_HDR_SIZE - parser->header_read_pos;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(&(parser->header_buf[parser->header_read_pos]), data_ptr, cpSize);
        parser->header_read_pos += cpSize;
        data_ptr += cpSize;
        //size -= cpSize;

        parse_buf = (parser->header_read_pos == CBUS_MSG_HDR_SIZE) ? parser->header_buf : NULL;
    }

    if (parse_buf)
    {/** parse header data */  
        GU32 length = 0;
        memcpy(&length, parse_buf, 4);
        parse_buf += 4;

        if (length >= CBUS_MSG_HDR_SIZE)
        {
            GU32 payload_size = length - CBUS_MSG_HDR_SIZE;
            parser->message = cbus_message_alloc(payload_size);

            if (!parser->message)
            {
                LOGE("alloc message failed\n");
                return data_ptr;
            }

            //LOGD("[%s.%d]===>: msg length: %d, payload len: %d\n", __FUNCTION__, __LINE__, length, payload_size);
            cbus_message_t message = parser->message;

            cbus_message_set_message_id(message, *(((cbus_message_id*)parse_buf)));
            parse_buf += sizeof(cbus_message_id);
            cbus_message_set_session_id(message, *(((cbus_session_id*)parse_buf)));
            parse_buf += sizeof(cbus_session_id);
            
            cbus_message_set_protocol_version(message, *parse_buf++);
            cbus_message_set_interface_version(message, *parse_buf++);
            cbus_message_set_message_type(message, *parse_buf++);
            cbus_message_set_return_code(message, *parse_buf++);

            cbus_message_set_endpoint_id(message, parser->endpoint_id);
            cbus_message_set_endpoint_ref(message, parser->opaque);
            cbus_message_set_opaque(message, parser->opaque);

            if (length > CBUS_MSG_HDR_SIZE)
            {
                parser->payload_read_pos = 0;
                parser->payload_size = payload_size;
                parser->state = CBUS_MSG_PARSE_STATE_PAYLOAD;
            }
            else
            {
                parser->state = CBUS_MSG_PARSE_STATE_END;
            }
        }
        else
        {
            parser->state = CBUS_MSG_PARSE_STATE_START;            
        }        
    }

    return data_ptr;
}

static const GU08* cbus_message_packet_deserializer_on_parse_payload(cbus_message_packet_deserializer_t parser, const GU08* data_ptr, const GU08* end_ptr)
{
    //LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    if (parser->payload_read_pos < parser->payload_size)
    {   
        GS32 size = (GS32)(end_ptr - data_ptr);
        GS32 cpSize = (GS32)(parser->payload_size - parser->payload_read_pos);
        cpSize = (cpSize <= size) ? cpSize : size;
        if (parser->message)
        {
            memcpy(cbus_message_get_payload(parser->message) + parser->payload_read_pos, data_ptr, cpSize); 
        }
        
        parser->payload_read_pos += cpSize;
        data_ptr += cpSize;
    }

    if (parser->payload_read_pos == parser->payload_size)
    {
        parser->state = CBUS_MSG_PARSE_STATE_END;
    }

    return data_ptr;
}

static const GU08* cbus_message_packet_deserializer_on_parse_end(cbus_message_packet_deserializer_t parser, const GU08* data_ptr, const GU08* end_ptr)
{
    //LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    //LOGD("===>: size: %d\n", size);
    if (*data_ptr == CBUS_MSG_PKT_END_CODE)
    {
        data_ptr++;
        //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        if (parser->message && parser->on_message)
        {
            /** this message will be free in the callback */   
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);         
           // LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
            cbus_message_set_opaque(parser->message, parser->opaque);

            parser->on_message(parser->message);            
            //LOGD("[%s.%d]===>:\n", __FUNCTION__, __LINE__);
        }

        cbus_message_free(parser->message);
        parser->message = NULL;
    }
    //LOGD("===>: size: %d\n", size);

    parser->state = CBUS_MSG_PARSE_STATE_START;
    return data_ptr;
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

cbus_message_packet_deserializer_t cbus_message_packet_deserializer_create(cbus_endpoint_id endpoint_id, cbus_message_packet_deserializer_on_message_func on_message, GPTR opaque)
{
    if (!on_message)
    {
        return NULL;
    }

    cbus_message_packet_deserializer_t parser = (cbus_message_packet_deserializer_t)malloc(sizeof(struct _cbus_message_packet_deserializer));

    if (parser)
    {
        memset(parser, 0, sizeof(struct _cbus_message_packet_deserializer));
        parser->endpoint_id = endpoint_id;
        parser->on_message  = on_message;
        parser->opaque      = opaque;
       // LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    }

    return parser;
}

void cbus_message_packet_deserializer_destroy(cbus_message_packet_deserializer_t parser)
{
    if (parser)
    {        
        free(parser);
    }
}

void cbus_message_packet_deserializer_check_valid(cbus_message_packet_deserializer_t parser, const char* check_at, int check_line)
{
    LOGD("[%s.%d]===>:check at(%s.%d)parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, check_at, check_line,
         parser, parser->on_message, parser->message, parser->opaque);
}

GErrc cbus_message_packet_deserializer_deserialize(cbus_message_packet_deserializer_t parser, const GBUF buf, GU32 size)
{
   // LOGD("[%s.%d]===>:parser: %p, on_message: %p, message: %p, opaque: %p\n", __FUNCTION__, __LINE__, parser, parser->on_message, parser->message, parser->opaque);
    const GU08* data_ptr = (GU08*)buf;
    const GU08* end_ptr = buf + size;
    //LOGD("===>: size: %d\n", size);
    while (data_ptr < end_ptr)
    {
        switch (parser->state)
        {
        case CBUS_MSG_PARSE_STATE_START:
            //LOGD("[%s.%d]===>: size: %d, start: 0x%x, offset: %d\n", __FUNCTION__, __LINE__, size, *data_ptr, (data_ptr - buf));
            data_ptr = cbus_message_packet_deserializer_on_parse_start(parser, data_ptr, end_ptr);
            break;
        case CBUS_MSG_PARSE_STATE_HEADER:
           // LOGD("[%s.%d]===>: size: %d, start: 0x%x, offset: %d\n", __FUNCTION__, __LINE__, size, *data_ptr, (data_ptr - buf));
            data_ptr = cbus_message_packet_deserializer_on_parse_header(parser, data_ptr, end_ptr);
            break; 
        case CBUS_MSG_PARSE_STATE_PAYLOAD:
            //LOGD("[%s.%d]===>: size: %d, offset: %d\n", __FUNCTION__, __LINE__, size, (data_ptr - buf));
            data_ptr = cbus_message_packet_deserializer_on_parse_payload(parser, data_ptr, end_ptr);
            
            break;
        case CBUS_MSG_PARSE_STATE_END:
            //LOGD("[%s.%d]===>: size: %d, offset: %d\n", __FUNCTION__, __LINE__, size, (data_ptr - buf));
            data_ptr = cbus_message_packet_deserializer_on_parse_end(parser, data_ptr, end_ptr);
            
            break;
        default:
            LOGD("[%s.%d]===>: size: %d\n", __FUNCTION__, __LINE__, size);
            return G_ErrInvalidStatus;
            break;
        }
    }

    return G_OK; /** todo, refineme */
}

#if 0
cbus_buffer_t cbus_message_packet_serializer_serialize(const cbus_message_t* message)
{
    if (!message)
    {
        return CBUS_BUFFER_INVALID;
    }
    //LOGD("[%s.%d]===>: msg id: %d, type: %d, session: %d\n", __FUNCTION__, __LINE__, message->message_id, message->message_type, message->session_id);

    GU32 length = message->payload ? (CBUS_MSG_HDR_SIZE + cbus_buffer_size(message->payload)) : CBUS_MSG_HDR_SIZE;

    cbus_buffer_t buf = cbus_buffer_alloc(length + 2); /** +2 indicat, start code(1byte), end code(1byte) */

    if (buf != CBUS_BUFFER_INVALID)
    {
        GBUF out_buf = cbus_buffer_data(buf);

        CBUS_MSG_PKT_SET(out_buf, CBUS_MSG_PKT_OFFSET_START_CODE,    GU08, CBUS_MSG_PKT_START_CODE);
        CBUS_MSG_PKT_CPY(out_buf, CBUS_MSG_PKT_OFFSET_LENGTH,        &length, sizeof(length));
        CBUS_MSG_PKT_CPY(out_buf, CBUS_MSG_PKT_OFFSET_MESSAGE_ID,    &message->message_id, sizeof(GU16));
        CBUS_MSG_PKT_CPY(out_buf, CBUS_MSG_PKT_OFFSET_SESSION_ID,    &message->session_id, sizeof(GU16));
        CBUS_MSG_PKT_SET(out_buf, CBUS_MSG_PKT_OFFSET_PROTO_VER,     GU08, message->protocol_version);
        CBUS_MSG_PKT_SET(out_buf, CBUS_MSG_PKT_OFFSET_INTERFACE_VER, GU08, message->interface_version);
        CBUS_MSG_PKT_SET(out_buf, CBUS_MSG_PKT_OFFSET_MESSAGE_TYPE,  GU08, message->message_type);
        CBUS_MSG_PKT_SET(out_buf, CBUS_MSG_PKT_OFFSET_RETURN_CODE,   GU08, message->return_code);        



        /**todo, use reserved buffer to instead of cbus_buffer_copy */

        if (message->payload != CBUS_BUFFER_INVALID)
        {
            /** +1 indicate include start code(1byte) */
            memcpy(cbus_buffer_data(buf) + CBUS_MSG_HDR_SIZE + 1, cbus_buffer_data(message->payload), cbus_buffer_size(message->payload));            
        }

        CBUS_MSG_PKT_SET(out_buf, (length + 1), GU08, CBUS_MSG_PKT_END_CODE); /** +1 indicate include start code(1byte) */
    }

    return buf;    
}
#endif